﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using ca2simulator.shared.wcf;
using ca2simulator.server.console.simulator;
using ca2simulator.shared;
using ca2simulator.server.console.simulator.basic.transfer;
using ca2simulator.server.console.simulator.basic;

namespace ca2simulator.server.console.wcf
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "CA2Service" in both code and config file together.
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
    public class CA2Service : ICA2SimulationService, ISimulatorListener
    {
        List<ISimulationCallback> subscribers = new List<ISimulationCallback>();

        ISimulator _Simulator;

        #region CA2 Serive methods
        public LatticeContract GetLattice()
        {
            return new LatticeContract(_Simulator.GetLattice());
        }
        public bool Run()
        {
            return _Simulator.Run();
        }
        public bool Halt()
        {
            return _Simulator.Halt();
            
        }
        public LatticeContract Step(int numStep)
        {
            for (int i = 0; i < numStep; i++)
            {
                _Simulator.Step();
            }
            return new LatticeContract(_Simulator.GetLattice());
        }

        public bool UploadSimulation(CAContract ca)
        {
            CA2 ca2;
            if (CA2Factory.BuildCA2(ca.Data, out ca2))
            {
                _Simulator = new BasicSimulator(ca2);
                _Simulator.AddListener(this);
                return true;
            } return false;
        }
        public bool ReleaseSimulation()
        {
            if (_Simulator.Halt())
            {
                _Simulator.RemoveListener(this);
                _Simulator = null;
                return true;
            } 
            return false;
        }

        #region Subscribe/unsubscribe and announce methods
        // From: http://idunno.org/archive/2008/05/29/wcf-callbacks-a-beginners-guide.aspx

        public bool Subscribe()
        {
            try
            {
                ISimulationCallback callback = OperationContext.Current.GetCallbackChannel<ISimulationCallback>();
                if (!subscribers.Contains(callback))
                    subscribers.Add(callback);
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool Unsubscribe()
        {
            try
            {
                ISimulationCallback callback = OperationContext.Current.GetCallbackChannel<ISimulationCallback>();
                if (subscribers.Contains(callback))
                    subscribers.Remove(callback);
                return true;
            }
            catch
            {
                return false;
            }
        }

        private void AnnounceUpdate(Lattice lattice)
        {
            subscribers.ForEach(delegate(ISimulationCallback callback)
            {
                LatticeContract lat = new LatticeContract(lattice);
                if (((ICommunicationObject)callback).State == CommunicationState.Opened)
                {
                    callback.OnLatticeUpdate(lat);
                }
                else
                {
                    subscribers.Remove(callback);
                }
            });
        }
        private void AnnounceUpdate(CellChange[] changes)
        {
            subscribers.ForEach(delegate(ISimulationCallback callback)
            {
                LatticeChangeContract lat = new LatticeChangeContract(changes);
                if (((ICommunicationObject)callback).State == CommunicationState.Opened)
                {
                    callback.OnLatticeChange(lat);
                }
                else
                {
                    subscribers.Remove(callback);
                }
            });
        }

        #endregion
        #endregion

        #region Simulation Listener methods
        public void OnLatticeUpdate()
        {
            //TODO Remove commented out code!
            //this.AnnounceUpdate(_Simulator.Simulator.GetLattice());
            this.AnnounceUpdate(_Simulator.GetChanges());
        }

        public void OnSimulationFinish()
        {
            throw new NotImplementedException();
        }
        #endregion



        public void Start()
        {
            
        }
    }

    /// <summary>
    /// Basic Life simulator used for testing, initializes on server
    /// </summary>
    public class LifeSimulator : ISimulator
    {
        private ISimulator _Sim;
        public ISimulator Simulator { get { return _Sim; } }

        public LifeSimulator()
        {
            CellState stateDead = new CellState();
            stateDead.State = 0;
            CellState stateLive = new CellState();
            stateLive.State = 1;

            Lattice iniLat = new Lattice(stateDead, 800, 800);
            for (int i = 0; i < 20; i++)
            {
                addPattern(iniLat, getPatternByName("Glider"), 1 + 5 * i, 1, stateLive);
            }
            //TODO Transfer function needs uploaded from client
            CA2 ca = new CA2(new BasicLifeTransfer(), iniLat);
            //CA2 ca = new CA2(lat, new StaticTransfer(), iniLat);
            _Sim = new BasicSimulator(ca);
        }

        private static void addPattern(Lattice lat, List<CellLocation> pattern, int r, int c, CellState stateToSet)
        {
            Cell cellValue = new Cell();
            cellValue.CellState = stateToSet;
            foreach (CellLocation cs in pattern)
            {
                lat[r + cs.row, c + cs.col] = cellValue;
            }
        }

        private static List<CellLocation> getPatternByName(string pattern)
        {
            List<CellLocation> cells = new List<CellLocation>();
            if ("Glider".Equals(pattern, StringComparison.OrdinalIgnoreCase))
            {
                cells.Add(new CellLocation(0, 1));
                cells.Add(new CellLocation(1, 2));
                cells.Add(new CellLocation(2, 0));
                cells.Add(new CellLocation(2, 1));
                cells.Add(new CellLocation(2, 2));
            }
            else if ("Block".Equals(pattern, StringComparison.OrdinalIgnoreCase))
            {
                cells.Add(new CellLocation(0, 0));
                cells.Add(new CellLocation(0, 1));
                cells.Add(new CellLocation(1, 0));
                cells.Add(new CellLocation(1, 1));
            }

            return cells;

        }

        public bool Step(int numSteps)
        {
            return _Sim.Step(numSteps);
        }

        public bool Run()
        {
            return _Sim.Run();
        }

        public bool Halt()
        {
            return _Sim.Halt();
        }

        public bool Step()
        {
            return _Sim.Step();
        }

        public Lattice GetLattice()
        {
            return _Sim.GetLattice();
        }

        public CellChange[] GetChanges()
        {
            return _Sim.GetChanges();
        }

        public bool AddListener(ISimulatorListener listener)
        {
            return _Sim.AddListener(listener);
        }

        public bool RemoveListener(ISimulatorListener listener)
        {
            return _Sim.RemoveListener(listener);
        }
    }

}
